package interfaces.subpanel.classifier;

import interfaces.mainpanel.gui.TesisMainGUI;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.commons.lang3.StringUtils;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.RectangleInsets;
import org.joda.time.DateTime;

import utils.ImagenesUtils;
import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.classifiers.bayes.NaiveBayes;
import weka.classifiers.functions.LibSVM;
import weka.classifiers.functions.SMO;
import weka.classifiers.lazy.IBk;
import weka.classifiers.trees.J48;
import weka.core.Instance;
import weka.core.Instances;
import weka.gui.beans.ShadowBorder;

public class ClassifierGUI extends JPanel implements ActionListener, ListSelectionListener {

	private static final String RESULTS_CHART = "Results Chart";

	private static final String CLASSIFIER = "Classifier";

	private static final String AMOUNT = "Amount";

	private static final String TIME_PATTERN = "hh:mm:ss";

	private static final String NEW_LINE = "\n";

	private static final long serialVersionUID = 1L;

	private TesisMainGUI mainGUI;

	private JList listResults;

	private DefaultListModel listResultsModel;

	private JTextArea resultDetailsArea;

	private String[] classifiersStrings = { " Naive Bayes ", " k-NN (1) ", " k-NN (3) ", " J48 ", " SVM (SMO) " };

	private JButton classifyButton, clearDataButton;

	private JComboBox classifierList;

	private Classifier classifierSelected = new NaiveBayes();

	private Instances filteredInstances, testingManualInstances;

	private JFreeChart resultChart;

	private ConfusionMatrix confusionMatrix = new ConfusionMatrix();

	private DefaultCategoryDataset chartDataSet = new DefaultCategoryDataset();

	private JPanel chartPanel;

	public ClassifierGUI(TesisMainGUI mainGUI) {

		super(new BorderLayout(5, 5));

		this.mainGUI = mainGUI;

		this.classifyButton = new JButton("Classify Instances", ImagenesUtils.createImageIcon(this.getClass(), "/processing.png"));
		this.classifyButton.addActionListener(this);

		this.clearDataButton = new JButton("Clear Data", ImagenesUtils.createImageIcon(this.getClass(), "/dropBox.png"));
		this.clearDataButton.addActionListener(this);

		JPanel classifyPanel = new JPanel(new BorderLayout(8, 2));
		this.classifierList = new JComboBox(this.classifiersStrings);
		this.classifierList.setSelectedIndex(0);
		this.classifierList.addActionListener(this);
		JLabel classifierLabel = new JLabel("Classifier: ", JLabel.LEFT);
		classifyPanel.add(classifierLabel, BorderLayout.WEST);
		classifyPanel.add(this.classifierList);

		this.listResultsModel = new DefaultListModel();
		this.listResults = new JList(this.listResultsModel);
		this.listResults.setCellRenderer(new ResultCellRenderer(ImagenesUtils.createImageIcon(this.getClass(), "/check.png")));
		this.listResults.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		this.listResults.setSelectedIndex(0);
		this.listResults.addListSelectionListener(this);

		JScrollPane resultsPane = new JScrollPane(this.listResults);
		resultsPane.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Classifications Results: "),
						BorderFactory.createEmptyBorder(5, 5, 5, 5)), resultsPane.getBorder()));

		this.resultDetailsArea = new JTextArea();
		this.resultDetailsArea.setBorder(new ShadowBorder(2, Color.LIGHT_GRAY));
		this.resultDetailsArea.setLineWrap(false);
		this.resultDetailsArea.setWrapStyleWord(false);
		this.resultDetailsArea.setEditable(false);
		this.resultDetailsArea.setColumns(37);
		this.resultDetailsArea.setMargin(new Insets(3, 3, 3, 3));
		JScrollPane panelDetailsArea = new JScrollPane(this.resultDetailsArea);

		JPanel summaryPanel = new JPanel(new GridLayout(2, 1));
		JPanel topSummaryPanel = new JPanel(new BorderLayout(5, 5));
		topSummaryPanel.add(panelDetailsArea, BorderLayout.WEST);
		topSummaryPanel.add(this.confusionMatrix, BorderLayout.CENTER);
		ChartPanel footerSummaryPanel = this.createEmptyPieTestingChart(RESULTS_CHART, CLASSIFIER, AMOUNT);
		this.chartPanel = new JPanel();
		this.chartPanel.add(footerSummaryPanel);
		summaryPanel.add(topSummaryPanel);
		summaryPanel.add(footerSummaryPanel);

		summaryPanel.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Classifications Results: "),
						BorderFactory.createEmptyBorder(5, 5, 5, 5)), summaryPanel.getBorder()));

		JPanel buttonsPanel = new JPanel(new GridLayout(1, 2, 5, 5));
		buttonsPanel.add(this.classifyButton);
		buttonsPanel.add(this.clearDataButton);

		JPanel selectionPanel = new JPanel(new GridLayout(2, 1, 5, 5));
		selectionPanel.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Classifiers Panel: "),
						BorderFactory.createEmptyBorder(5, 5, 5, 5)), selectionPanel.getBorder()));
		selectionPanel.add(classifyPanel);
		selectionPanel.add(buttonsPanel);

		JPanel leftPanel = new JPanel(new BorderLayout(5, 5));
		leftPanel.add(selectionPanel, BorderLayout.PAGE_START);
		leftPanel.add(resultsPane, BorderLayout.CENTER);

		this.add(leftPanel, BorderLayout.WEST);
		this.add(summaryPanel, BorderLayout.CENTER);

	}

	private ChartPanel createEmptyPieTestingChart(String title, String xDescription, String ydescription) {
		this.resultChart = ChartFactory.createBarChart(title, xDescription, ydescription, this.chartDataSet, PlotOrientation.VERTICAL, true, true,
				false);
		this.resultChart.setBorderVisible(false);
		this.resultChart.setBackgroundPaint(null);
		this.resultChart.getTitle().setPaint(Color.GRAY);
		this.resultChart.setPadding(new RectangleInsets(0, 0, 0, 0));
		CategoryPlot mainPlot = this.resultChart.getCategoryPlot();
		mainPlot.setRangeGridlinePaint(Color.red);
		mainPlot.setBackgroundPaint(Color.WHITE);
		NumberAxis mainAxis = (NumberAxis) mainPlot.getRangeAxis();
		mainAxis.setLowerBound(0);
		mainAxis.setUpperBound(650);
		ChartPanel chartPanel = new ChartPanel(this.resultChart);
		chartPanel.setMinimumDrawWidth(550);

		return chartPanel;
	}

	public void valueChanged(ListSelectionEvent e) {
		if (e.getValueIsAdjusting() == false && this.listResults.getSelectedIndex() != -1) {
			ResultClassifyEvaluation resultEval = (ResultClassifyEvaluation) this.listResults.getSelectedValue();
			this.resultDetailsArea.setText(resultEval.getResultEval());
			this.confusionMatrix.fillConfusionMatrix(resultEval.getCmMatrix());
		} else if (e.getValueIsAdjusting() == false && this.listResults.getSelectedIndex() == -1)
			this.confusionMatrix.clearMatrix();
	}

	public void actionPerformed(ActionEvent e) {

		if (e.getSource() == this.classifierList) {

			switch (this.classifierList.getSelectedIndex()) {
			case 0:
				this.classifierSelected = new NaiveBayes();
				break;
			case 1:
				this.classifierSelected = new IBk(1);
				break;
			case 2:
				this.classifierSelected = new IBk(3);
				break;
			case 3:
				this.classifierSelected = new J48();
				break;
			case 4:
				this.classifierSelected = new SMO();
				break;
			case 5:
				this.classifierSelected = new LibSVM();
				break;
			}

		} else if (e.getSource() == this.classifyButton) {

			this.classifyInstances();

		} else if (e.getSource() == this.clearDataButton) {

			this.clearData();
		}
	}

	private void classifyInstances() {
		try {
			if (this.filteredInstances != null) {

				if (this.mainGUI.getLoadDataSetGUI().isCrossValidation() && !this.mainGUI.getLoadDataSetGUI().validateFolds())
					return;

				this.classifierSelected.buildClassifier(this.filteredInstances);
				Evaluation test = new Evaluation(this.filteredInstances);
				if (this.mainGUI.getLoadDataSetGUI().isCrossValidation())
					test.crossValidateModel(this.classifierSelected, this.filteredInstances,
							Integer.parseInt(this.mainGUI.getLoadDataSetGUI().getFoldsForCrossValidation()), new Random(1));
				else if (this.mainGUI.getLoadDataSetGUI().isUseTrainingSetRb())
					test.evaluateModel(this.classifierSelected, this.filteredInstances);
				else if (this.mainGUI.getLoadDataSetGUI().isManualSelectionSplitRb()
						|| this.mainGUI.getLoadDataSetGUI().isPercentageSelectionSplitRb()) {
					test.evaluateModel(this.classifierSelected, this.testingManualInstances);
				}

				String divisionAlgorithm = "Test Option: ";
				if (this.mainGUI.getLoadDataSetGUI().isCrossValidation())
					divisionAlgorithm = divisionAlgorithm + "Cross-Validation" + " Num. Folds = "
							+ this.mainGUI.getLoadDataSetGUI().getFoldsForCrossValidation();
				else if (this.mainGUI.getLoadDataSetGUI().isUseTrainingSetRb())
					divisionAlgorithm = divisionAlgorithm + "Use Training Set";
				else if (this.mainGUI.getLoadDataSetGUI().isManualSelectionSplitRb())
					divisionAlgorithm = divisionAlgorithm + "Manual Selection Split" + "(TrainSet = "
							+ this.mainGUI.getLoadDataSetGUI().getTrainingDataTable().getAllGenericScenaries().size() + ", TestSet = "
							+ this.mainGUI.getLoadDataSetGUI().getTestingDataTable().getAllGenericScenaries().size() + ")";
				else if (this.mainGUI.getLoadDataSetGUI().isPercentageSelectionSplitRb())
					divisionAlgorithm = divisionAlgorithm + "Percentage Split" + "(TraiSet = "
							+ this.mainGUI.getLoadDataSetGUI().getTrainingDataTable().getAllGenericScenaries().size() + ", TestSet = "
							+ this.mainGUI.getLoadDataSetGUI().getTestingDataTable().getAllGenericScenaries().size() + ")";

				String classifier = "Classifier: " + this.classifierList.getSelectedItem();
				String stemmer = "Stemmer: " + this.mainGUI.getPreprocessingGUI().getStemmerName();
				String tokenizer = "Tokenizer: " + this.mainGUI.getPreprocessingGUI().getTokenizerName();
				String id = "C" + String.valueOf(this.listResultsModel.size() + 1);
				String title = id + " - " + this.classifierList.getSelectedItem().toString() + "(" + new DateTime().toString(TIME_PATTERN) + ")";

				Instances instancesFunctionalToBeClustered = (this.mainGUI.getLoadDataSetGUI().isCrossValidation() || this.mainGUI.getLoadDataSetGUI()
						.isUseTrainingSetRb()) ? new Instances(this.filteredInstances) : new Instances(this.testingManualInstances);
				Instances instancesQualityToBeClustered = (this.mainGUI.getLoadDataSetGUI().isCrossValidation() || this.mainGUI.getLoadDataSetGUI()
						.isUseTrainingSetRb()) ? new Instances(this.filteredInstances) : new Instances(this.testingManualInstances);
						
				// Verifico cuales fueron clasificadas como funcionales para
				// luego hacer el clustering.
				Iterator<Instance> it = (this.mainGUI.getLoadDataSetGUI().isCrossValidation() || this.mainGUI.getLoadDataSetGUI()
						.isUseTrainingSetRb()) ? this.filteredInstances.iterator() : this.testingManualInstances.iterator();
				List<Instance> functionalIntances = new ArrayList<Instance>();
				List<Instance> qualityInstances = new ArrayList<Instance>();
				while (it.hasNext()) {
					Instance instance = it.next();
					double clas = this.classifierSelected.classifyInstance(instance);
					// El 1.0 es porque la clase FUNCTIONAL se encuentra en la
					// primera posicion de la lista.
					if (clas != 1.0) {
						qualityInstances.add(instance);
					} else {
						functionalIntances.add(instance);
					}
				}
				instancesFunctionalToBeClustered.clear();
				instancesQualityToBeClustered.clear();
				instancesFunctionalToBeClustered.addAll(functionalIntances);
				instancesQualityToBeClustered.addAll(qualityInstances);

				ResultClassifyEvaluation result = new ResultClassifyEvaluation(id, title, " ======= Classifing Summary ======= " + NEW_LINE
						+ NEW_LINE + divisionAlgorithm + NEW_LINE + classifier + NEW_LINE + stemmer + NEW_LINE + tokenizer + NEW_LINE
						+ test.toSummaryString(), test.confusionMatrix(), instancesFunctionalToBeClustered, instancesQualityToBeClustered);
				this.listResultsModel.addElement(result);
				this.listResults.setSelectedIndex(this.listResultsModel.size() - 1);
				this.addNewClassifierToChart(id, this.mainGUI.getLoadDataSetGUI().getTotalNumberOfInstances(), (int) test.correct(),
						(int) test.incorrect());
				this.resultDetailsArea.setCaretPosition(0);
				// Agrego los resultados tambien a la lista para el clustering.
				this.mainGUI.getClusteringGUI().addElementToResultList(result);
			} else
				JOptionPane.showMessageDialog(this, "You must load and filtered the data set to be classified!", "Empty Instances!",
						JOptionPane.ERROR_MESSAGE, ImagenesUtils.createImageIcon(this.getClass(), "/stop.png"));
		} catch (Exception e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), "ERROR!", JOptionPane.ERROR_MESSAGE,
					ImagenesUtils.createImageIcon(this.getClass(), "/stop.png"));
		}
	}

	private void addNewClassifierToChart(String id, int total, int correct, int incorrect) {
		this.chartDataSet.addValue(incorrect, "Incorrect", id);
		this.chartDataSet.addValue(correct, "Correct", id);
		this.chartPanel.revalidate();
		this.chartPanel.repaint();
	}

	public void clearData() {
		this.classifierList.setSelectedIndex(0);
		this.resultDetailsArea.setText(StringUtils.EMPTY);
		if (this.listResultsModel != null)
			this.listResultsModel.clear();
		this.chartDataSet.clear();
		this.chartPanel.revalidate();
		this.chartPanel.repaint();
		this.mainGUI.getClusteringGUI().resetData();
	}

	public TesisMainGUI getMainGUI() {
		return mainGUI;
	}

	public Instances getFilteredInstances() {
		return filteredInstances;
	}

	public void setFilteredInstancesForTraining(Instances filteredInstances) {
		this.filteredInstances = filteredInstances;
	}

	public void setFilteredInstancesForTesting(Instances testingManualInstances) {
		this.testingManualInstances = testingManualInstances;
	}
}